#include "Demo.h"
#include "../xEngine/D3d11Utils.h"

#include "../xEngine/Types.h"
#include "../xEngine/AssimpLoader.h"
#include "../xEngine/ResourceManager.h"
#include "../xEngine/Geometry.h"
#include "../xEngine/RenderTexture.h"
#include "../xEngine/TextureUtils.h"
#include "../xEngine/MathUtils.h"

Demo::Demo(HINSTANCE hInstance): super(hInstance),
	mpDrawShadowMapFx(NULL), mpShadowMapQuad(NULL), mShadowLightRotDeg(0.0f)
{
}

Demo::~Demo()
{
	SAFE_RELEASE(mpDirLightFx);
	SAFE_RELEASE(mpDrawShadowMapFx);
	SAFE_RELEASE(mpConstBuf);
	SAFE_RELEASE(mpShadowMap);
	
	SAFE_RELEASE(mpShadowMapQuad);

	std::vector<Entity*>::iterator entIt = mEntities.begin();
	
	while( entIt != mEntities.end() )
	{
		SAFE_RELEASE((*entIt));
		entIt++;
	}
}

// Override
void Demo::Init()
{
	super::Init();

		// Layout with normal ("directional light" and "render with shadows" 

	D3D11_INPUT_ELEMENT_DESC layoutDescArrayDl[] =	// See Vertex class in Types.h for clarification
	{
		{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 }, 
		{ "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 }, 
		{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 36, D3D11_INPUT_PER_VERTEX_DATA, 0 }, 
	};

	InputLayoutDesc inLayoutDesc;
	inLayoutDesc.mDescSize = 3;
	inLayoutDesc.mpDesc = layoutDescArrayDl;
	
	mpDirLightFx = Effect::Create(mpDeviceManager, &inLayoutDesc, "..\\Game\\Shaders\\RenderWithTexture.hlsl", "VSMain", "PSMain", NULL, NULL, NULL, false);
	
	mpRenderWithShadowFx = Effect::Create(mpDeviceManager, &inLayoutDesc, "..\\Game\\Shaders\\RenderWithShadows.hlsl", "VSMain", "PSMain", NULL, NULL, NULL, false);

		// Layout without normal ("draw shadow map" shader)

	D3D11_INPUT_ELEMENT_DESC layoutDescArrayDs[] =
	{
		{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 }, 
		{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 36, D3D11_INPUT_PER_VERTEX_DATA, 0 }
	};
	
	inLayoutDesc.mDescSize = 2;
	inLayoutDesc.mpDesc = layoutDescArrayDs;

	mpDrawShadowMapFx = Effect::Create(mpDeviceManager, &inLayoutDesc, "..\\Game\\Shaders\\DrawShadowMap.hlsl", "VSMain", "PSMain", NULL, NULL, NULL, false);

		// Create const buffer

	mpConstBuf = ConstantBuffer::Create(mpDeviceManager, sizeof(Transform));

		// Cameras

	FLOAT aspect = (FLOAT)mpDeviceManager->GetWidth() / (FLOAT)mpDeviceManager->GetHeight();

	mCamera.SetPosition(XMVectorSet(0,20,-50,0));
	mCamera.SetUp(XMVectorSet(0,1,0,0));
	mCamera.LookAt(XMVectorSet(0,0,0,0));
	mCamera.SetPerspective(0.25f*PI, aspect, 0.5f , 1000.0f);
	//mCamera.SetOrthographic(30.0f, 30.0f, 1.0f, 1000.f);
	mCamera.RebuildView();
	
	mShadowLight.SetPosition(XMVectorSet(0, 30, 0, 0));
	mShadowLight.SetUp(XMVectorSet(1,0,0,0));
	mShadowLight.LookAt(XMVectorSet(0,0,0,0));
	
	// mShadowLight.SetPerspective(0.25f*PI, aspect, 0.5f , 100.0f);
	mShadowLight.SetOrthographic(30.0f, 30.0f, 1.0f, 100.f);
	mShadowLight.RebuildView();

		// Init meshes
	
	InitAxes();
	InitFloor();
	InitSpaceship();
	InitTextureQuad();
	InitShadowMapQuad();

		// Init rasterizer state

	ID3D11RasterizerState * rasterState;

	D3D11_RASTERIZER_DESC desc;

	desc.FillMode = D3D11_FILL_SOLID;
	// desc.FillMode = D3D11_FILL_WIREFRAME;
	desc.CullMode = D3D11_CULL_BACK;
	desc.FrontCounterClockwise = false;
	desc.DepthBias = 0;
	desc.DepthBiasClamp = 0;
	desc.SlopeScaledDepthBias = 0;
	desc.DepthClipEnable = true;
	desc.ScissorEnable = false;
	desc.MultisampleEnable = true;
	desc.AntialiasedLineEnable = true;
	
	HR(mpDeviceManager->mpDevice->CreateRasterizerState( &desc, &rasterState));

	mpDeviceManager->mpContext->RSSetState(rasterState);

		// Create shadow map

	mpShadowMap = new RenderTexture(mpDeviceManager, 1024, 1024, true, DXGI_FORMAT_R32G32B32A32_FLOAT);
	
}

void Demo::InitSpaceship()
{
	mBuilding.mName = "Building";

	char filename[] = "..\\Game\\Mesh\\building15.lwo";

	Mesh* pMesh = ResourceManager::GetInst()->GetMesh(filename);

	if (pMesh != NULL)
	{
		mBuilding.mpGraphic = pMesh;
		mBuilding.mPosition = XMFLOAT3(10,5,0);
		mBuilding.mFront = XMFLOAT3(0,0,1);
		mBuilding.mUp = XMFLOAT3(0,1,0);
		mBuilding.mScaling = 0.5;
		mEntities.push_back(&mBuilding);
	}
	else
	{
		std::string errMsg = "Unable to load ";
		errMsg += filename;
		MessageBoxA(mhMainWnd, errMsg.c_str(), "Error", MB_OK);
		Abort();
	}

}

void Demo::InitFloor()
{
	mFloor.mName = "Floor";

	MeshData* floorData;
	Geometry::CreateTiledQuad(11, 11, 10.0, &floorData);

	floorData->mpSubsets[0].mDiffuseMapFilePath = "..\\Game\\Texture\\floor_diffuse.dds";
	
	mFloor.mpGraphic = Mesh::Create(floorData, mpDeviceManager);
	mFloor.mPosition = XMFLOAT3(0,0,0);
	mFloor.mFront = XMFLOAT3(0,0,1);
	mFloor.mUp = XMFLOAT3(0,1,0);
	delete floorData;
	mEntities.push_back(&mFloor);
}

void Demo::InitTextureQuad()
{
	mTextureQuad.mName = "TextureQuad";

	MeshData* quadData;
	Geometry::CreateTiledQuad(2, 2, 30.0, &quadData);

	mTextureQuad.mpGraphic = Mesh::Create(quadData, mpDeviceManager);
	mTextureQuad.mPosition = XMFLOAT3(0,0,0);
	mTextureQuad.mFront = XMFLOAT3(0,1,0);
	mTextureQuad.mUp = XMFLOAT3(0,0,-1);
	delete quadData;
}

void Demo::InitAxes()
{
	mAxes.mName = "Axes";

	MeshData* axesData;
	Geometry::CreateAxes(10, 0.5, &axesData);
	
	XMCOLOR red(1,0,0,1);
	ID3D11Texture2D *redTex = TextureUtils::MakeColorTexture(mpDeviceManager->mpDevice, 32, 32, red);
	ID3D11ShaderResourceView* redTexSrv;
	HR(mpDeviceManager->mpDevice->CreateShaderResourceView(redTex, 0, &redTexSrv));
	SAFE_RELEASE(redTex);

	XMCOLOR green(0,1,0,1);
	ID3D11Texture2D *greenTex = TextureUtils::MakeColorTexture(mpDeviceManager->mpDevice, 32, 32, green);
	ID3D11ShaderResourceView* greenTexSrv;
	HR(mpDeviceManager->mpDevice->CreateShaderResourceView(greenTex, 0, &greenTexSrv));
	SAFE_RELEASE(greenTex);	

	XMCOLOR blue(0,0,1,1);
	ID3D11Texture2D *blueTex = TextureUtils::MakeColorTexture(mpDeviceManager->mpDevice, 32, 32, blue);
	ID3D11ShaderResourceView* blueTexSrv;
	HR(mpDeviceManager->mpDevice->CreateShaderResourceView(blueTex, 0, &blueTexSrv));
	SAFE_RELEASE(blueTex);

	mAxes.mpGraphic = Mesh::Create(axesData, mpDeviceManager);
	mAxes.mpGraphic->GetMeshPart(0)->mpDiffuseMap = redTexSrv;
	mAxes.mpGraphic->GetMeshPart(1)->mpDiffuseMap = greenTexSrv;
	mAxes.mpGraphic->GetMeshPart(2)->mpDiffuseMap = blueTexSrv;
	mAxes.mPosition = XMFLOAT3(0,0,0);
	mAxes.mFront = XMFLOAT3(0,0,1);
	mAxes.mUp = XMFLOAT3(0,1,0);
	delete axesData;
	mEntities.push_back(&mAxes);
}

void Demo::InitShadowMapQuad()
{
	MeshData* quadData;
	Geometry::CreateScreenQuad(XMFLOAT2(0.5f,0.5f), XMFLOAT2(1.0f, 1.0f), &quadData);
	mpShadowMapQuad = Mesh::Create(quadData, mpDeviceManager);
	delete quadData;
}

// Override
void Demo::Update(float dt)
{
	float speed = 15;

	if(GetAsyncKeyState('A') & 0x8000)
	{
		mCamera.Strafe(-speed * dt);
	}
	if(GetAsyncKeyState('D') & 0x8000)
	{
		mCamera.Strafe(speed * dt);
	}
	if(GetAsyncKeyState('W') & 0x8000)
	{
		mCamera.Walk(speed * dt);
	}
	if(GetAsyncKeyState('S') & 0x8000)
	{
		mCamera.Walk(-speed * dt);
	}

	if(GetAsyncKeyState('O') & 0x8000)
	{
		mShadowLight.Walk(-speed * dt);
	}
	if(GetAsyncKeyState('P') & 0x8000)
	{
		mShadowLight.Walk(speed * dt);
	}

	XMVECTOR shipPos = XMLoadFloat3(&mBuilding.mPosition);

	mShadowLight.SetUp(XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f));

	mShadowLight.LookAt(shipPos);

	FLOAT radius = 50.0f;
	
	FLOAT deltaAngleDeg = 100.0f * dt;

	mShadowLightRotDeg += deltaAngleDeg;
	if (mShadowLightRotDeg > 360.0f)
	{
		mShadowLightRotDeg -= 360.0f;
	}

	FLOAT shadowLightRotRad = mShadowLightRotDeg * PI / 180.0f;

	FLOAT offsetX = radius * XMScalarCos(shadowLightRotRad);
	FLOAT offsetZ = radius * XMScalarSin(shadowLightRotRad);

	XMVECTOR lightPos = XMVectorAdd(shipPos, XMVectorSet(offsetX, 30.0f, offsetZ, 0.0f));

	mShadowLight.SetPosition(lightPos);

	mCamera.RebuildView();
	mShadowLight.RebuildView();
}

// Override
void Demo::Draw()
{
	mpDeviceManager->Clear(1,1,1,1);
	
	mpDirLightFx->Apply();
	
	mpShadowMap->Begin();
	//DrawEntities();
	// DrawEntity(mFloor);
	DrawEntity(mBuilding, mShadowLight);
	mpShadowMap->End();
	
	mpDeviceManager->RestoreTarget();

	//mpDirLightFx->Apply();

	mpRenderWithShadowFx->Apply();

	// DrawEntities();
	
	// mTextureQuad.mpGraphic->GetMeshPart(0)->mpDiffuseMap = mpShadowMap->GetColorMap();

	// mpDirLightFx->Apply();

	DrawEntityWithShadow(mAxes);
	DrawEntityWithShadow(mFloor);
	DrawEntityWithShadow(mBuilding);

	DrawCameraQuad(true);

	mpDeviceManager->Present();
}

// Private

void Demo::DrawCameraQuad(bool shadowMap)
{
	if (shadowMap)
	{
		mpShadowMapQuad->GetMeshPart(0)->mpDiffuseMap = mpShadowMap->GetDepthMap();
		mpDrawShadowMapFx->Apply();
		
		mpShadowMapQuad->mpInputLayout = mpDrawShadowMapFx->GetInputLayout();
	}
	else
	{
		mpShadowMapQuad->GetMeshPart(0)->mpDiffuseMap = mpShadowMap->GetColorMap();
		mpDirLightFx->Apply();
		
		mpShadowMapQuad->mpInputLayout = mpDirLightFx->GetInputLayout();

		Transform trans = Transform();
		trans.mWorld = XMMatrixIdentity();
		trans.mWorldViewProj =  XMMatrixIdentity();
		trans.mLightDirection = XMVector3Normalize(XMVectorSet(0,1,1,0));
		
		mpConstBuf->Write(&trans, sizeof(Transform));
		ID3D11Buffer* buffers[1] = {mpConstBuf->GetBufferPointer()};
		mpDeviceManager->mpContext->VSSetConstantBuffers(0, 1, buffers);
		mpDeviceManager->mpContext->PSSetConstantBuffers(0, 1, buffers);
	}
	
	ID3D11ShaderResourceView* views[1]={mpShadowMapQuad->GetMeshPart(0)->mpDiffuseMap};
	mpDeviceManager->mpContext->PSSetShaderResources(0, 1, views);
	mpShadowMapQuad->Draw(0);
	
	views[0] = NULL;
	mpDeviceManager->mpContext->PSSetShaderResources(0, 1, views);
}

// Private

void Demo::DrawEntities()
{
	std::vector<Entity*>::iterator entIt = mEntities.begin();

	while( entIt != mEntities.end() )
	{
		DrawEntity(**entIt);
		entIt++;
	}
}

// Private

void Demo::DrawEntity(Entity &entity)
{
	DrawEntity(entity, mCamera);
}

void Demo::DrawEntity(Entity &entity, Camera &camera)
{
	Transform trans = Transform();
	Mesh* pMesh;

	trans.mWorld = XMLoadFloat4x4(&entity.GetWorld());
	trans.mWorldViewProj = trans.mWorld * camera.View() *  camera.Proj();
	trans.mLightDirection = XMVector3Normalize(XMVectorSet(0,1,1,0));

	mpConstBuf->Write(&trans, sizeof(Transform));
	ID3D11Buffer* buffers[1] = {mpConstBuf->GetBufferPointer()};
	mpDeviceManager->mpContext->VSSetConstantBuffers(0, 1, buffers);
	mpDeviceManager->mpContext->PSSetConstantBuffers(0, 1, buffers);

	pMesh = entity.mpGraphic;

	pMesh->mpInputLayout = mpDirLightFx->GetInputLayout();

	for(int i=0;i<pMesh->GetMeshCount();i++)
	{		
		ID3D11ShaderResourceView* views[1]={pMesh->GetMeshPart(i)->mpDiffuseMap};
		mpDeviceManager->mpContext->PSSetShaderResources(0, 1, views);
		pMesh->Draw(i);
	}
}

void Demo::DrawEntityWithShadow(Entity &entity)
{
	DrawEntityWithShadow(entity, mCamera);
}

void Demo::DrawEntityWithShadow(Entity &entity, Camera &camera)
{
	TransformShadow trans = TransformShadow();
	Mesh* pMesh;

	XMMATRIX world = XMLoadFloat4x4(&entity.GetWorld());
	trans.mWorldViewProj = world * camera.View() *  camera.Proj();
	trans.mLightWorldViewProj = world * mShadowLight.View() * mShadowLight.Proj();


	mpConstBuf->Write(&trans, sizeof(TransformShadow));
	ID3D11Buffer* buffers[1] = {mpConstBuf->GetBufferPointer()};
	mpDeviceManager->mpContext->VSSetConstantBuffers(0, 1, buffers);
	mpDeviceManager->mpContext->PSSetConstantBuffers(0, 1, buffers);

	ID3D11ShaderResourceView* shadowMaps[1]={mpShadowMap->GetDepthMap()};

	pMesh = entity.mpGraphic;

	pMesh->mpInputLayout = mpRenderWithShadowFx->GetInputLayout();

	ID3D11SamplerState* samplerState;
	
	D3D11_SAMPLER_DESC sampD=D3D11_SAMPLER_DESC();
	sampD.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;
	sampD.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
	sampD.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;
	sampD.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
	
	mpDeviceManager->mpDevice->CreateSamplerState(&sampD, &samplerState);
	
	ID3D11SamplerState* samps[1] = {samplerState};
	mpDeviceManager->mpContext->PSSetSamplers(1, 1, samps);
	
	for(int i=0;i<pMesh->GetMeshCount();i++)
	{		
		ID3D11ShaderResourceView* views[1]={pMesh->GetMeshPart(i)->mpDiffuseMap};
		mpDeviceManager->mpContext->PSSetShaderResources(0, 1, views);

		mpDeviceManager->mpContext->PSSetShaderResources(1, 1, shadowMaps);

		pMesh->Draw(i);
	}
	
	ID3D11ShaderResourceView* nullArray[1]={NULL};

	mpDeviceManager->mpContext->PSSetShaderResources(0, 1, nullArray);
	mpDeviceManager->mpContext->PSSetShaderResources(1, 1, nullArray);
}

LRESULT Demo::MsgProc(UINT msg, WPARAM wParam, LPARAM lParam)
{
	POINT mousePos;
	int dx = 0;
	int dy = 0;
	switch(msg)
	{
	case WM_LBUTTONDOWN:
		if( wParam & MK_LBUTTON )
		{
			SetCapture(mhMainWnd);

			mOldMousePos.x = LOWORD(lParam);
			mOldMousePos.y = HIWORD(lParam);
		}
		return 0;

	case WM_LBUTTONUP:
		ReleaseCapture();
		return 0;

	case WM_MOUSEMOVE:
		if( wParam & MK_LBUTTON )
		{
			mousePos.x = (int)LOWORD(lParam); 
			mousePos.y = (int)HIWORD(lParam); 

			dx = mousePos.x - mOldMousePos.x;
			dy = mousePos.y - mOldMousePos.y;

			mCamera.Pitch( dy * 0.0087266f );
			mCamera.RotateY( dx * 0.0087266f );
			
			mOldMousePos = mousePos;
		}
		return 0;
	}

	return super::MsgProc(msg, wParam, lParam);
}